package com.tianji.promotion.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.BooleanUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.discount.Discount;
import com.tianji.promotion.discount.DiscountStrategy;
import com.tianji.promotion.domain.dto.CouponDiscountDTO;
import com.tianji.promotion.domain.dto.OrderCourseDTO;
import com.tianji.promotion.domain.dto.UserCouponDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.UserCouponQuery;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.enums.ExchangeCodeStatus;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import com.tianji.promotion.utils.CodeUtil;
import com.tianji.promotion.utils.PermuteUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户领取优惠券的记录，是真正使用的优惠券信息 服务实现类
 * </p>
 *
 * @author author
 * @since 2025-04-22
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {
    private final CouponMapper couponMapper;
    private final StringRedisTemplate redisTemplate;
    private final IExchangeCodeService exchangeCodeService;
    private final RedissonClient redissonClient;
    private final RabbitMqHelper mqHelper;
    private final ICouponScopeService couponScopeService;
    //private final IUserCouponService self;

    @Override
    public List<UserCoupon> queryByCouponId(Long user, List<Long> couponIds) {

        return this.lambdaQuery()
                .eq(UserCoupon::getUserId,user)
                .in(UserCoupon::getCouponId,couponIds)
                .list();
    }

    @Override
    /*public void addUserCoupon(Long id) {
        Coupon coupon = couponMapper.selectById(id);
        Long userId = UserContext.getUser();
        //新增条件:1.优惠卷最大发行数量>当前已发行数量 2.用户已领取数量<用户限领数量
        if (coupon == null || coupon.getStatus() != CouponStatus.ISSUING){
            throw new RuntimeException("优惠券不存在或不在发行中");
        }
            *//*UserCoupon userCoupon = new UserCoupon();
            userCoupon.setUserId(userId);
            userCoupon.setCouponId(id);
            userCoupon.setStatus(UserCouponStatus.UNUSED);
            save(userCoupon);
            coupon.setIssueNum(coupon.getIssueNum()+1);
            couponMapper.updateById(coupon);*//*
        RLock lock = redissonClient.getLock("lock:coupon:uid:" + userId);
        boolean isLock = lock.tryLock();
        if (!isLock){
            throw  new RuntimeException("请求太频繁");
        }
        try{
            // saveUserCouponAndCouponUpdate(userId,coupon,null);
            IUserCouponService userCouponService = (IUserCouponService) AopContext.currentProxy();
            userCouponService.saveUserCouponAndCouponUpdate(userId, coupon, null);
        }finally {
            lock.unlock();
        }
    }*/
    public void addUserCoupon(Long id) {
      String key=PromotionConstants.COUPON_CACHE_KEY_PREFIX+id;
        Long userId = UserContext.getUser();
            Coupon coupon=queryCouponByCache(key,id);
        //新增条件:1.优惠卷最大发行数量>当前已发行数量 2.用户已领取数量<用户限领数量
        /*if (coupon == null || coupon.getStatus() != CouponStatus.ISSUING){
            throw new RuntimeException("优惠券不存在或不在发行中");
        }*/
            /*UserCoupon userCoupon = new UserCoupon();
            userCoupon.setUserId(userId);
            userCoupon.setCouponId(id);
            userCoupon.setStatus(UserCouponStatus.UNUSED);
            save(userCoupon);
            coupon.setIssueNum(coupon.getIssueNum()+1);
            couponMapper.updateById(coupon);*/
        if (coupon == null){
            throw new RuntimeException("优惠券不存在");
        }
        if (coupon.getIssueBeginTime().isAfter(LocalDateTime.now()) || coupon.getIssueEndTime().isBefore(LocalDateTime.now())){
            throw new RuntimeException("优惠券不在发行时间");
        }
        RLock lock = redissonClient.getLock("lock:coupon:uid:" + userId);
        boolean isLock = lock.tryLock();
        if (!isLock){
            throw  new RuntimeException("请求太频繁");
        }
        //将用户优惠券信息保存到redis中，++
        log.info("key的id{}", coupon.getId());
        Long increment = redisTemplate.opsForHash().increment(PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + coupon.getId(), userId.toString(), 1);
        log.info("increment:{}", increment);
        boolean flag = coupon.getTotalNum() > 0 && increment <=coupon.getUserLimit();
        if (!flag){
            redisTemplate.opsForHash().increment(PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + coupon.getId(), userId.toString(), -1);
            throw new RuntimeException("优惠券已领完");
        }
        //更新redis优惠券总数量
        redisTemplate.opsForHash().increment(PromotionConstants.COUPON_CACHE_KEY_PREFIX + coupon.getId(), "totalNum", -1);
        log.info("用户优惠券信息保存到redis中，++");
        try{
            // saveUserCouponAndCouponUpdate(userId,coupon,null);
            //saveUserCouponAndCouponUpdate1(userId, coupon, null);
            //mq异步发送消息

            UserCouponDTO dto = new UserCouponDTO();
            dto.setUserId(userId);
            dto.setCouponId(id);
            dto.setParsedCode(null);
            mqHelper.send(MqConstants.Exchange.PROMOTION_EXCHANGE, MqConstants.Key.COUPON_RECEIVE,dto);
            log.info("发送MQ消息到交换机：{}，路由键：{}",MqConstants.Exchange.PROMOTION_EXCHANGE,MqConstants.Key.COUPON_RECEIVE);
        }finally {
            lock.unlock();
        }
    }

    private Coupon queryCouponByCache(String key,Long id) {
        Map<Object, Object> objMap = redisTemplate.opsForHash().entries(key);
        Coupon coupon = new Coupon();
        if (objMap.isEmpty()) {
            return null;
        }
        for (Map.Entry<Object, Object> entry : objMap.entrySet()) {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
            switch (entry.getKey().toString()) {
                case "issueBeginTime":

                    LocalDate date1 = LocalDate.parse(entry.getValue().toString(), formatter);
                    coupon.setIssueBeginTime(date1.atStartOfDay()); // 补充时间为00:00:00
                    break;
                case "issueEndTime":
                    LocalDate date2 = LocalDate.parse(entry.getValue().toString(), formatter);
                    coupon.setIssueEndTime(date2.atStartOfDay()); // 补充时间为00:00:00
                    break;
                case "totalNum":
                    coupon.setTotalNum(Integer.parseInt(entry.getValue().toString()));
                    break;
                case"userLimit":
                    coupon.setUserLimit(Integer.parseInt(entry.getValue().toString()));
                    break;
            }
        }
        coupon.setId(id);
        return  coupon;
    }

    @Transactional
    public void saveUserCouponAndCouponUpdate(Long userId, Coupon coupon,Long parsedCode) {
        //新增条件:1.优惠卷最大发行数量>当前已发行数量 2.用户已领取数量<用户限领数量
        Integer count = lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getCouponId, coupon.getId()).count();
        boolean flag = coupon.getTotalNum() > coupon.getIssueNum() && count < coupon.getUserLimit();
        if (!flag){
            throw new RuntimeException("优惠券已领完");
        }
        int i = couponMapper.incrIssueNum(coupon.getId());//更新优惠券发行数量 issue_num++
        if (i!=1){
            throw new RuntimeException("优惠券已领完");
        }
        saveUserCoupon(userId, coupon);//增加用户优惠券
        if (parsedCode!=null){
            //更新兑换码状态
            exchangeCodeService.lambdaUpdate()
                    .set(ExchangeCode::getUserId, userId)
                    .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED)
                    .eq(ExchangeCode::getId, parsedCode)
                    .update();
        }
    }
    public void saveUserCouponAndCouponUpdate1(Long userId, Coupon coupon,Long parsedCode) {
        //新增条件:1.优惠卷最大发行数量>当前已发行数量 2.用户已领取数量<用户限领数量
        /*Integer count = lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getCouponId, coupon.getId()).count();*/
        /*//将用户优惠券信息保存到redis中，++
        Long increment = redisTemplate.opsForHash().increment(PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + coupon.getId(), userId.toString(), 1);

        boolean flag = coupon.getTotalNum() > coupon.getIssueNum() && increment < coupon.getUserLimit();
        if (!flag){
            throw new RuntimeException("优惠券已领完");
        }
        //更新优惠券发行数量 issue_num++
        redisTemplate.opsForHash().increment(PromotionConstants.COUPON_CACHE_KEY_PREFIX + coupon.getId(), "issueNum", 1);*/
        /**
         * 可以将这段db操作通过mq进行优化
        * */

        int i = couponMapper.incrIssueNum(coupon.getId());//更新优惠券发行数量 issue_num++
        if (i!=1){
            throw new RuntimeException("优惠券已领完");
        }
        Coupon coupon1 = couponMapper.selectById(coupon.getId());
        saveUserCoupon(userId, coupon1);//增加用户优惠券
        if (parsedCode!=null){
            //更新兑换码状态
            exchangeCodeService.lambdaUpdate()
                    .set(ExchangeCode::getUserId, userId)
                    .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED)
                    .eq(ExchangeCode::getId, parsedCode)
                    .update();
        }
    }

    @Override
    public PageDTO<CouponPageVO> queryMyCouponPage(UserCouponQuery query) {
        //获取当前登录用户id
        Long userId = UserContext.getUser();
        Page<UserCoupon> page = lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getStatus, query.getStatus())
                .page(query.toMpPage("term_end_time", true));
        List<UserCoupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)){
            return PageDTO.empty(page);
        }
        //获取couponIds集合
        List<Long> couponIds = records.stream().map(UserCoupon::getCouponId).collect(Collectors.toList());
        List<Coupon> coupons = couponMapper.selectBatchIds(couponIds);
        List<CouponPageVO> list = BeanUtil.copyToList(coupons, CouponPageVO.class);
        return PageDTO.of(page, list);
    }

    @Override
    public void checkAndCreateUserCoupon(UserCouponDTO dto) {
        int i = couponMapper.incrIssueNum(dto.getCouponId());//更新优惠券发行数量 issue_num++
        if (i!=1){
            throw new RuntimeException("优惠券已领完");
        }
        Coupon coupon = couponMapper.selectById(dto.getCouponId());
        saveUserCoupon(dto.getUserId(), coupon);//增加用户优惠券
        if (dto.getParsedCode()!=null){
            //更新兑换码状态
            exchangeCodeService.lambdaUpdate()
                    .set(ExchangeCode::getUserId, dto.getUserId())
                    .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED)
                    .eq(ExchangeCode::getId, dto.getParsedCode())
                    .update();
        }
    }



    private void saveUserCoupon(Long userId, Coupon coupon) {
        UserCoupon userCoupon = new UserCoupon();
        if (coupon.getTermDays()!=null){
            userCoupon.setTermBeginTime(LocalDateTime.now());
            userCoupon.setTermEndTime(LocalDateTime.now().plusDays(coupon.getTermDays()));
        }else {
            userCoupon.setTermBeginTime(coupon.getTermBeginTime());
            userCoupon.setTermEndTime(coupon.getTermEndTime());
        }
        userCoupon.setUserId(userId);
        userCoupon.setCouponId(coupon.getId());
        userCoupon.setStatus(UserCouponStatus.UNUSED);
        save(userCoupon);
    }
    @Override
    public void exchangeCoupon(String code) {
        if (code==null){
            throw  new BizIllegalException("优惠券兑换码不能为空");
        }
        //解析验证码，得到自增id
        long parsedCode = CodeUtil.parseCode(code);
        //根据bitMap中的返回值判断该兑换码是否被使用过，返回true代表使用过
        Boolean aBoolean = redisTemplate.opsForValue().setBit(PromotionConstants.COUPON_CODE_MAP_KEY, parsedCode, true);
        if (BooleanUtil.isTrue(aBoolean)){
            throw new BizIllegalException("优惠券兑换码已被使用过");
        }
        try {
            //查询db，判断该优惠卷是否存在
            ExchangeCode byId = exchangeCodeService.getById(parsedCode);
            if (byId==null){
                throw new BizIllegalException("优惠券兑换码不存在");
            }
            //查询db，判断该优惠卷是否以及开始兑换或者过期
            if (byId.getExpiredTime().isBefore(LocalDateTime.now())){
                throw new BizIllegalException("优惠券兑换码已过期");
            }
            if (byId.getStatus()!= ExchangeCodeStatus.UNUSED){
                throw new BizIllegalException("优惠券兑换码已被使用过");
            }
            Coupon coupon = couponMapper.selectById(byId.getExchangeTargetId());

            RLock lock = redissonClient.getLock("lock:exchange:coupon:codeId:" + parsedCode);
            boolean isLock = lock.tryLock();
            if (!isLock){
                throw  new RuntimeException("请求太频繁");
            }
            try{
               /* // saveUserCouponAndCouponUpdate(userId,coupon,null);
                IUserCouponService userCouponService = (IUserCouponService) AopContext.currentProxy();
                userCouponService.saveUserCouponAndCouponUpdate(UserContext.getUser(),coupon,parsedCode);*/
                //通过mq异步发送信息
                UserCouponDTO dto = new UserCouponDTO();
                dto.setUserId(UserContext.getUser());
                dto.setCouponId(coupon.getId());
                dto.setParsedCode(parsedCode);
                mqHelper.send(MqConstants.Exchange.PROMOTION_EXCHANGE,
                        MqConstants.Key.COUPON_RECEIVE,dto);
                log.info("优惠券兑换成功，优惠券id：{}",coupon.getId());
            }finally {
                lock.unlock();
            }

        } catch (BizIllegalException e) {
            log.error("优惠券兑换码兑换失败，原因：{}",e.getMessage());
            redisTemplate.opsForValue().setBit(PromotionConstants.COUPON_CODE_MAP_KEY, parsedCode, false);
            throw new RuntimeException(e);
        }
    }
    @Override
    public List<CouponDiscountDTO> findDiscountSolution(List<OrderCourseDTO> orderCourses) {
        //1.获取当前登录用户
        Long userId = UserContext.getUser();
        //2.联表查询优惠券获取折扣等信息 条件： 用户，状态为未使用，过期时间大于当前时间
        List<Coupon> coupons= couponMapper.queryMyCoupons(userId);
        if (coupons.isEmpty()){
            log.info("优惠券不存在可用的优惠券");
            return List.of();
        }
        //3.粗筛 计算课程总价值判断每个优惠卷是否达到门槛
        int totalAmount = orderCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();
        coupons= coupons.stream()
                .filter(coupon -> DiscountStrategy.getDiscount(coupon.getDiscountType()).canUse(totalAmount, coupon))
                .collect(Collectors.toList());
        if (coupons.isEmpty()){
            log.info("优惠券不存在可用的优惠券");
            return List.of();
        }
        //4.细筛 查询每隔优惠卷的作用范围 判断每个优惠卷是否能用于客户选择的某个课程
        Map<Coupon,List<OrderCourseDTO>> couponListMap = queryCouponScope(coupons, orderCourses);
        //5.获得细筛过后的优惠卷集合，即map的所有key
        coupons= new ArrayList<>(couponListMap.keySet());
        //6.对可用优惠卷进行排列组合
        List<List<Coupon>> couponPermutations = PermuteUtil.permute(coupons);
        List<CouponDiscountDTO> list = new ArrayList<>(couponPermutations.size());
        for (List<Coupon> couponList : couponPermutations) {
            CouponDiscountDTO dto = new CouponDiscountDTO();
            //折扣详情集合 key为课程id，value为每个课程的优惠金额 由于某个优惠卷可能对一些课程适用，对某些课程不适用，所以当我们记录某个优惠卷
            Map<Long, Integer> detailMap = orderCourses.stream().collect(Collectors.toMap(OrderCourseDTO::getId, v -> 0));
            //计算每一种优惠卷组合的优惠卷优惠金额 返回值为总共优惠金额
            int discountAmount = calculationEveryCouponCombinationAmount(couponList, couponListMap, detailMap);
            //封装优惠卷详情 该组合的优惠卷优惠金额，优惠卷id集合
            dto.setDiscountAmount(discountAmount);
            dto.setIds(couponList.stream().map(Coupon::getCreater).collect(Collectors.toList()));
            //封装优惠卷规则
            dto.setRules(queryCouponRule(couponList));
            //封装优惠卷明细
            dto.setDiscountDetail(detailMap);
            list.add(dto);
        }

        return result(list,coupons,totalAmount);
    }

    /**
     * 封装返回对象 ：：
     *
     * 上述我们得到的list就是每一种经过排列组合得到的优惠卷组合的优惠卷优惠金额，优惠卷id集合，优惠卷规则
     * 但前端要求返回的是：：1：这些优惠卷组合中优惠力度最大的组合  2.经过细筛后所有能用的单张优惠卷
     * */
    private List<CouponDiscountDTO> result(List<CouponDiscountDTO> list, List<Coupon> coupons, int totalAmount) {
        //1.返回list中优惠力度最大的组合 sorted:升序 reverse:倒序,翻转 limit:限制返回数量
        list= list.stream().sorted(Comparator.comparingInt(CouponDiscountDTO::getDiscountAmount).reversed())
                .limit(1)
                .collect(Collectors.toList());
        for (Coupon coupon : coupons) {
            CouponDiscountDTO dto = new CouponDiscountDTO();
            dto.setIds(Collections.singletonList(coupon.getCreater()));
            dto.setDiscountAmount(DiscountStrategy.getDiscount(coupon.getDiscountType()).calculateDiscount(totalAmount, coupon));
            dto.setRules(Collections.singletonList(DiscountStrategy.getDiscount(coupon.getDiscountType()).getRule(coupon)));
            dto.setDiscountDetail(new HashMap<>());
            list.add(dto);
        }
        return list;
    }

    /**
     * 返回该优惠卷组合的每个优惠卷规则
     * */
    private List<String> queryCouponRule(List<Coupon> couponList) {
        ArrayList<String> list = new ArrayList<>(couponList.size());
        for (Coupon coupon : couponList) {
            String rule = DiscountStrategy.getDiscount(coupon.getDiscountType()).getRule(coupon);
            list.add(rule);
        }
        return list;
    }

    /**
     * 计算每一种排列组合的优惠卷方案
     * */
    private int calculationEveryCouponCombinationAmount(List<Coupon> couponList,
                                                        Map<Coupon, List<OrderCourseDTO>> couponListMap, Map<Long, Integer> detailMap) {
        int maxDiscountAmount = 0;
        for (Coupon coupon : couponList) {
            //该优惠卷能够优惠的课程集合
            List<OrderCourseDTO> orderCourseDTOS = couponListMap.get(coupon);
            //计算得到已经优惠的金额
            int alreadyDiscountAmount=0;
            for (Map.Entry<Long, Integer> entry : detailMap.entrySet()) {
                alreadyDiscountAmount += entry.getValue();
            }
            //计算该课程总金额 总价格-上个优惠卷优惠的金额
            int totalAmount = orderCourseDTOS.stream().mapToInt(OrderCourseDTO::getPrice).sum()-alreadyDiscountAmount;
            Discount discountType = DiscountStrategy.getDiscount(coupon.getDiscountType());
            if (discountType.canUse(totalAmount, coupon)){
                int discount = discountType.calculateDiscount(totalAmount, coupon);
                maxDiscountAmount+=discount;
                //计算每种优惠卷打折扣后的相关课程的优惠金额，方便下次计算课程总价值
                calculationDiscountDetail(orderCourseDTOS, detailMap, discount,totalAmount);
            }
        }
        return maxDiscountAmount;
    }

    /**
     * 计算每种排列组合中的每个优惠卷打折扣后的相关课程的优惠金额，方便下次计算课程总价值
     * */
    private void calculationDiscountDetail( List<OrderCourseDTO> orderCourseDTOS, Map<Long, Integer> detailMap, int discount, int totalAmount) {
        int count=0;
        int remainDiscountAmount=discount;
        for (OrderCourseDTO orderCourseDTO : orderCourseDTOS) {
            count++;
            if (count==orderCourseDTOS.size()){
                detailMap.put(orderCourseDTO.getId(),detailMap.get(orderCourseDTO.getId())+remainDiscountAmount);
                break;
            }
            //1.每个课程的单价
            Integer price = orderCourseDTO.getPrice();
            //计算得到已经优惠的金额
            int everyDiscountAmount=price*discount/totalAmount;
            remainDiscountAmount=remainDiscountAmount-everyDiscountAmount;
            detailMap.put(orderCourseDTO.getId(),detailMap.get(orderCourseDTO.getId())+everyDiscountAmount);
        }
    }

    private Map<Coupon,  List<OrderCourseDTO>> queryCouponScope(List<Coupon> coupons
            , List<OrderCourseDTO> orderCourses) {
        Map<Coupon, List<OrderCourseDTO>> map = new HashMap<>();
        for (Coupon coupon : coupons) {
            //1.如果getSpecific为true，证明有限定范围
            if (coupon.getSpecific()){
                //2.获取优惠券的业务id集合
                List<CouponScope> couponScopes = couponScopeService.lambdaQuery()
                        .eq(CouponScope::getCouponId, coupon.getId())
                        .list();
                List<Long> bizIds = couponScopes.stream().map(CouponScope::getBizId).collect(Collectors.toList());
                //3.初筛出满足该优惠卷适用范围的课程
                List<OrderCourseDTO> firstFilter = orderCourses.stream()
                        .filter(orderCourseDTO -> bizIds.contains(orderCourseDTO.getCateId()))
                        .collect(Collectors.toList());
                if (firstFilter.isEmpty()){
                    continue;
                }
                //4.细筛出满足该优惠卷的门槛值的优惠卷
                //4.1获取符合该优惠卷的课程的总价钱
                int totalAmount = firstFilter.stream().mapToInt(OrderCourseDTO::getPrice).sum();
                //4.2判断该优惠卷是否达到门槛
                if (!DiscountStrategy.getDiscount(coupon.getDiscountType()).canUse(totalAmount, coupon)){
                    continue;
                }
                map.put(coupon,firstFilter);
            }else {
                map.put(coupon,orderCourses);
            }
        }
        return map;
    }
}
